﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Microsoft.Research.Kinect.Nui;
using System.Reactive.Linq;
using System.Reactive;
using System.Reactive.Subjects;

namespace Kinect.KinectMouseControl
{
    public class KinectObserverCore : IKinect
    {
        private readonly bool _captureImageFrames;
        private readonly bool _captureDepthFrames;
        private Runtime _nui;

        public KinectObserverCore(bool captureImageFrames, bool captureDepthFrames)
        {
            _captureImageFrames = captureImageFrames;
            _captureDepthFrames = captureDepthFrames;
            _nui = new Runtime();

            SetupSkeletonFrameObserver();
            SetupImageFrameObserver();
            SetupDepthFrameObserver();
        }

        #region Skeleton Frame Publishing
        private IObservable<EventPattern<SkeletonFrameReadyEventArgs>> _skeletonFrameObservable;

        IObservable<SkeletonFrame> IEventPublisher<SkeletonFrame>.EventObservable
        {
            get { return _skeletonFrameSubject; }
        }

        private Subject<SkeletonFrame> _skeletonFrameSubject;

        private void SetupSkeletonFrameObserver()
        {
            _skeletonFrameSubject = new Subject<SkeletonFrame>();

            _skeletonFrameObservable = Observable.FromEventPattern<EventHandler<SkeletonFrameReadyEventArgs>, SkeletonFrameReadyEventArgs>(
                eh => _nui.SkeletonFrameReady += eh,
                eh => _nui.SkeletonFrameReady -= eh);

            _skeletonFrameObservable.Subscribe(x => _skeletonFrameSubject.OnNext(x.EventArgs.SkeletonFrame));
        }
        #endregion

        #region Image Frame Publishing
        private IObservable<EventPattern<ImageFrameReadyEventArgs>> _imageFrameObservable;

        IObservable<ImageFrame> IEventPublisher<ImageFrame>.EventObservable
        {
            get { return _imageFrameSubject; }
        }

        private Subject<ImageFrame> _imageFrameSubject;

        private void SetupImageFrameObserver()
        {
            _imageFrameSubject = new Subject<ImageFrame>();

            _imageFrameObservable = Observable.FromEventPattern<EventHandler<ImageFrameReadyEventArgs>, ImageFrameReadyEventArgs>(
                eh => _nui.VideoFrameReady += eh,
                eh => _nui.VideoFrameReady -= eh);

            _imageFrameObservable.Subscribe(x => _imageFrameSubject.OnNext(x.EventArgs.ImageFrame));
        }
        #endregion

        #region Depth Frame Publishing
        private IObservable<EventPattern<ImageFrameReadyEventArgs>> _depthFrameObservable;

        IObservable<DepthFrame> IEventPublisher<DepthFrame>.EventObservable
        {
            get { return _depthFrameSubject; }
        }

        private Subject<DepthFrame> _depthFrameSubject;

        private void SetupDepthFrameObserver()
        {
            _depthFrameSubject = new Subject<DepthFrame>();

            _depthFrameObservable = Observable.FromEventPattern<EventHandler<ImageFrameReadyEventArgs>, ImageFrameReadyEventArgs>(
                eh => _nui.DepthFrameReady += eh,
                eh => _nui.DepthFrameReady -= eh);

            _depthFrameObservable.Subscribe(x => _depthFrameSubject.OnNext(new DepthFrame(x.EventArgs.ImageFrame)));
        }
        #endregion

        public void Initialize()
        {
            _nui.Initialize(RuntimeOptions.UseDepthAndPlayerIndex | RuntimeOptions.UseSkeletalTracking | RuntimeOptions.UseColor);

            TransformSmoothParameters parameters;

            _nui.SkeletonEngine.TransformSmooth = true;

            //parameters = new TransformSmoothParameters
            //            {
            //                Smoothing = 1.0f,
            //                Correction = 0.1f,
            //                Prediction = 0.1f,
            //                JitterRadius = 0.05f,
            //                MaxDeviationRadius = 0.05f
            //            };

            parameters = new TransformSmoothParameters
            {
                Smoothing = .75f,
                Correction = 0.0f,
                Prediction = 0.0f,
                JitterRadius = 0.25f,
                MaxDeviationRadius = 0.1f
            };

            _nui.SkeletonEngine.SmoothParameters = parameters;

            if (_captureImageFrames)
            {
                _nui.VideoStream.Open(ImageStreamType.Video, 2, ImageResolution.Resolution640x480, ImageType.Color);
            }

            if (_captureDepthFrames)
            {
                _nui.DepthStream.Open(ImageStreamType.Depth, 2, ImageResolution.Resolution320x240,
                                      ImageType.DepthAndPlayerIndex);
            }

            _nui.NuiCamera.ElevationAngle = Properties.Settings.Default.KinectAngle;
        }

        #region IDisposable Members

        /// <summary>
        /// Internal variable which checks if Dispose has already been called
        /// </summary>
        private Boolean disposed;

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        private void Dispose(Boolean disposing)
        {
            if (disposed)
            {
                return;
            }

            if (disposing)
            {
                _nui.Uninitialize();
                //TODO: Managed cleanup code here, while managed refs still valid
            }
            //TODO: Unmanaged cleanup code here

            disposed = true;
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            // Call the private Dispose(bool) helper and indicate 
            // that we are explicitly disposing
            this.Dispose(true);

            // Tell the garbage collector that the object doesn't require any
            // cleanup when collected since Dispose was called explicitly.
            GC.SuppressFinalize(this);
        }

        #endregion
    }
}
